// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/options_product.h"
#include "coin2/exchange/deribit_options/symbology/currency.h"
#include "coin2/exchange/deribit_futures/symbology/product.h"

namespace coin2::exchange::deribit_options::symbology {

class DeribitOptionsProduct
    : public base::symbology::OptionsProduct<DeribitOptionsCurrency, DeribitOptionsProduct>,
      public base::symbology::ProductFromStrMixin<DeribitOptionsProduct> {
 public:
  using OptionsProduct = base::symbology::OptionsProduct<DeribitOptionsCurrency,
                                                         DeribitOptionsProduct>;
  using FuturesProduct = deribit_futures::symbology::DeribitFuturesProduct::FuturesProduct;
  using ContractType = base::symbology::ContractType;
  using OptionsSide = base::symbology::OptionsSide;
  using MarketType = base::market::MarketType;

 public:
  DeribitOptionsProduct(
      const DeribitOptionsCurrency& base,
      const DeribitOptionsCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const OptionsSide options_side,
      const int64_t strike_price,
      const int64_t resolve_timestamp,
      const int64_t strike_price_multiplier)
      : OptionsProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Deribit,
            contract_type,
            expiry_timestamp,
            options_side,
            strike_price,
            resolve_timestamp,
            strike_price_multiplier) {}

 public:
  static std::unique_ptr<DeribitOptionsProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    using base::symbology::IsDateStr;

    std::string expiry_str;
    std::string side_str;
    std::string strike_price_str;
    std::unique_ptr<DeribitOptionsCurrency> base;
    std::unique_ptr<DeribitOptionsCurrency> quote;
    std::vector<std::string> splitted;
    splitted = Split(native_product, "-");
    CHECK_EQ(splitted.size(), 4);

    base = DeribitOptionsCurrency::FromNativeStrNoThrow(splitted[0], resolve_timestamp);
    quote = DeribitOptionsCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    expiry_str = splitted[1];
    if (!deribit_futures::symbology::DeribitFuturesProduct::DateStrFromNative(
        expiry_str, &expiry_str)) {
      return {};
    }
    strike_price_str = splitted[2];
    side_str = splitted[3];

    if (!base) return {};

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    ContractType contract_type;
    int64_t expiry_timestamp;
    OptionsSide options_side;
    int64_t strike_price;
    int64_t strike_price_multiplier;
    bool success = CalculateParams(
        *base,
        *quote,
        expiry_str,
        side_str,
        strike_price_str,
        "1",
        resolve_timestamp,
        &native_product_tmp,
        &native_subscription_symbol,
        &contract_type,
        &expiry_timestamp,
        &options_side,
        &strike_price,
        &strike_price_multiplier);
    if (success) {
      DCHECK_EQ(native_product_tmp, native_product);
      return std::make_unique<DeribitOptionsProduct>(
          *base,
          *quote,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          options_side,
          strike_price,
          resolve_timestamp,
          strike_price_multiplier);
    } else {
      return {};
    }
  }

  static bool CalculateParams(
      const DeribitOptionsCurrency& base,
      const DeribitOptionsCurrency& quote,
      const std::string& expiry_str,
      const std::string& side_str,
      const std::string& strike_price_str,
      const std::string& strike_price_multiplier_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp,
      OptionsSide* options_side,
      int64_t* strike_price,
      int64_t* strike_price_multiplier) {
    using base::symbology::ContractType;
    using base::symbology::ExchangeType;
    using base::symbology::FindExpiryFriday;
    using base::symbology::FindExpiryDaily;
    using base::symbology::IsDateStr;
    using base::symbology::StringToContractType;
    using base::symbology::StringToOptionsSide;
    using base::symbology::ToDateStr;

    static const int64_t expiry_time_of_day_nanosec = 8 * 3600 * 1e9;
    auto side = StringToOptionsSide(side_str);
    if (side) {
      *options_side = side.value();
    } else {
      return false;
    }
    if (strike_price_str == "ALL") {
      *strike_price = 0;
    } else {
      *strike_price = stoll(strike_price_str);
    }
    *strike_price_multiplier = stoll(strike_price_multiplier_str);
    std::stringstream ss;

    // native_product
    ss.str(std::string());
    if (IsDateStr(expiry_str)) {
      bool found = false;
      for (auto type : {ContractType::DAY, ContractType::NEXT_DAY}) {
        auto timestamp = FindExpiryDaily(
            ExchangeType::Deribit, type, resolve_timestamp, expiry_time_of_day_nanosec);
        if (expiry_str == ToDateStr(timestamp)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;

          std::string native_expiry_str;
          deribit_futures::symbology::DeribitFuturesProduct::DateStrToNative(
              expiry_str, &native_expiry_str);
          ss << base.native_currency() << "-" << native_expiry_str
            << "-" << *strike_price << "-" << side_str;
          found = true;
          break;
        }
      }
      if (!found) {
        for (auto type : {ContractType::WEEK, ContractType::NEXT_WEEK,
                          ContractType::NNEXT_WEEK, ContractType::NNNEXT_WEEK,
                          ContractType::MONTH, ContractType::NEXT_MONTH,
                          ContractType::NNEXT_MONTH, ContractType::NNNEXT_MONTH,
                          ContractType::QUARTER, ContractType::NEXT_QUARTER,
                          ContractType::NNEXT_QUARTER, ContractType::NNNEXT_QUARTER}) {
          auto timestamp = FindExpiryFriday(
              ExchangeType::Deribit, type, resolve_timestamp, expiry_time_of_day_nanosec);
          if (expiry_str == ToDateStr(timestamp)) {
            *contract_type = type;
            *expiry_timestamp = timestamp;

            std::string native_expiry_str;
            deribit_futures::symbology::DeribitFuturesProduct::DateStrToNative(
                expiry_str, &native_expiry_str);
            ss << base.native_currency() << "-" << native_expiry_str
              << "-" << *strike_price << "-" << side_str;
            found = true;
            break;
          }
        }
      }
      if (!found) {
        return false;
      }
    } else {
      auto type = StringToContractType(expiry_str);
      if (type) {
        *contract_type = type.value();
        if (*contract_type == ContractType::PERPETUAL) {
          *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;
          ss << base.native_currency(MarketType::Futures) << "-PERPETUAL";
        } else {
          if (*contract_type == ContractType::DAY ||
              *contract_type == ContractType::NEXT_DAY) {
            *expiry_timestamp = FindExpiryDaily(ExchangeType::Deribit, *contract_type,
                                                resolve_timestamp, expiry_time_of_day_nanosec);
          } else {
            *expiry_timestamp = FindExpiryFriday(ExchangeType::Deribit, *contract_type,
                                                 resolve_timestamp, expiry_time_of_day_nanosec);
          }
          std::string date_str = ToDateStr(*expiry_timestamp);
          std::string native_date_str;
          deribit_futures::symbology::DeribitFuturesProduct::DateStrToNative(
              date_str, &native_date_str);
          ss << base.native_currency() << "-" << native_date_str
             << "-" << *strike_price << "-" << side_str;
        }
      } else {
        return false;
      }
    }
    *native_product = ss.str();
    *native_subscription_symbol = *native_product;

    return true;
  }
};

}  // namespace coin2::exchange::deribit_options::symbology
